﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

[assembly: CLSCompliant(true)]

namespace Rpm.Header
{
    [CLSCompliant(true)]
    public static class RpmHeader
    {
        public static Dictionary<string, List<object>> ReadHeaderTags(string fileName)
        {
            byte[] rpmHeaderMagic = { 0x8e, 0xad, 0xe8 };
            byte[] rpmFileMagic = { 0xed, 0xab, 0xee, 0xdb };

            Dictionary<string, List<object>> rpmTag = new Dictionary<string, List<object>>();

            try
            {
                using (FileStream fileStream = File.OpenRead(fileName))
                {
                    byte[] leadMagicBytes = new byte[4];
                    byte[] leadMajorBytes = new byte[1];
                    byte[] leadMinorBytes = new byte[1];
                    byte[] leadTypeBytes = new byte[2];
                    byte[] leadArchNumBytes = new byte[2];
                    byte[] leadNameBytes = new byte[66];
                    byte[] leadOsNumBytes = new byte[2];
                    byte[] leadSignatureTypeBytes = new byte[2];
                    byte[] leadReservedBytes = new byte[16];

                    fileStream.Read(leadMagicBytes, 0, leadMagicBytes.Length);

                    if (!leadMagicBytes.SequenceEqual(rpmFileMagic))
                        throw new Exception(string.Format("File {0} is not an RPM", fileName));

                    fileStream.Read(leadMajorBytes, 0, leadMajorBytes.Length);
                    fileStream.Read(leadMinorBytes, 0, leadMinorBytes.Length);
                    fileStream.Read(leadTypeBytes, 0, leadTypeBytes.Length);
                    fileStream.Read(leadArchNumBytes, 0, leadArchNumBytes.Length);
                    fileStream.Read(leadNameBytes, 0, leadNameBytes.Length);
                    fileStream.Read(leadOsNumBytes, 0, leadOsNumBytes.Length);
                    fileStream.Read(leadSignatureTypeBytes, 0, leadSignatureTypeBytes.Length);
                    fileStream.Read(leadReservedBytes, 0, leadReservedBytes.Length);

                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(leadTypeBytes);
                        Array.Reverse(leadArchNumBytes);
                        Array.Reverse(leadOsNumBytes);
                    }

                    string leadName = Encoding.ASCII.GetString(leadNameBytes);

                    for (int headerNum = 1; headerNum < 3; headerNum++)
                    {
                        byte[] headerMagicBytes = new byte[3];
                        byte[] headerVersionBytes = new byte[1];
                        byte[] headerReservedBytes = new byte[4];
                        byte[] headerEntriesBytes = new byte[4];
                        byte[] headerSizeBytes = new byte[4];
                        UInt32 headerReserved = 0;
                        UInt32 headerEntries = 0;
                        UInt32 headerSize = 0;

                        fileStream.Read(headerMagicBytes, 0, headerMagicBytes.Length);
                        fileStream.Read(headerVersionBytes, 0, headerVersionBytes.Length);
                        fileStream.Read(headerReservedBytes, 0, headerReservedBytes.Length);
                        fileStream.Read(headerEntriesBytes, 0, headerEntriesBytes.Length);
                        fileStream.Read(headerSizeBytes, 0, headerSizeBytes.Length);

                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(headerReservedBytes);
                            Array.Reverse(headerEntriesBytes);
                            Array.Reverse(headerSizeBytes);
                        }

                        headerReserved = BitConverter.ToUInt32(headerReservedBytes, 0);
                        headerEntries = BitConverter.ToUInt32(headerEntriesBytes, 0);
                        headerSize = BitConverter.ToUInt32(headerSizeBytes, 0);

                        if (headerMagicBytes.SequenceEqual(rpmHeaderMagic))
                        {
                            byte[] record = new byte[16 * headerEntries];
                            fileStream.Read(record, 0, record.Length);

                            byte[] buff;

                            if (headerNum == 1 && (headerSize % 8) != 0)
                            {
                                buff = new byte[headerSize + (8 - (headerSize % 8))];
                            }
                            else
                            {
                                buff = new byte[headerSize];
                            }

                            fileStream.Read(buff, 0, buff.Length);

                            if (headerNum == 1)
                            {
                                List<object> headerStart = new List<object>();
                                headerStart.Add(fileStream.Position);
                                rpmTag.Add("HEADER_START", headerStart);
                            }

                            for (int recordNum = 0; recordNum < headerEntries; recordNum++)
                            {
                                byte[] tagBytes = new byte[4];
                                byte[] typeBytes = new byte[4];
                                byte[] offsetBytes = new byte[4];
                                byte[] countBytes = new byte[4];
                                UInt32 tag = 0;
                                UInt32 type = 0;
                                UInt32 offset = 0;
                                UInt32 count = 0;

                                int recordOffset = recordNum * 16;
                                Array.Copy(record, recordOffset, tagBytes, 0, 4);
                                Array.Copy(record, recordOffset + 4, typeBytes, 0, 4);
                                Array.Copy(record, recordOffset + 8, offsetBytes, 0, 4);
                                Array.Copy(record, recordOffset + 12, countBytes, 0, 4);

                                if (BitConverter.IsLittleEndian)
                                {
                                    Array.Reverse(tagBytes);
                                    Array.Reverse(typeBytes);
                                    Array.Reverse(offsetBytes);
                                    Array.Reverse(countBytes);
                                }

                                tag = BitConverter.ToUInt32(tagBytes, 0);
                                type = BitConverter.ToUInt32(typeBytes, 0);
                                offset = BitConverter.ToUInt32(offsetBytes, 0);
                                count = BitConverter.ToUInt32(countBytes, 0);

                                List<object> value = new List<object>();

                                if (headerNum == 1)
                                    tag = tag * 10;

                                string tagName = Enum.GetName(typeof(RpmTag), tag);

                                if (tagName == null)
                                    continue;

                                if ((RpmTagType)type == RpmTagType.CHAR)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        byte[] valueBytes = new byte[1];
                                        Array.Copy(buff, offset, valueBytes, 0, 1);
                                        value.Add(BitConverter.ToString(valueBytes, 0));
                                        offset += 1;
                                    }
                                }
                                else if ((RpmTagType)type == RpmTagType.INT8)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        byte[] valueBytes = new byte[1];
                                        Array.Copy(buff, offset, valueBytes, 0, 1);

                                        if (BitConverter.IsLittleEndian)
                                            Array.Reverse(valueBytes);

                                        value.Add(BitConverter.ToInt16(valueBytes, 0));
                                        offset += 1;
                                    }
                                }
                                else if ((RpmTagType)type == RpmTagType.INT16)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        byte[] valueBytes = new byte[2];
                                        Array.Copy(buff, offset, valueBytes, 0, 2);

                                        if (BitConverter.IsLittleEndian)
                                            Array.Reverse(valueBytes);

                                        value.Add(BitConverter.ToInt16(valueBytes, 0));
                                        offset += 2;
                                    }
                                }
                                else if ((RpmTagType)type == RpmTagType.INT32)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        byte[] valueBytes = new byte[4];
                                        Array.Copy(buff, offset, valueBytes, 0, 4);

                                        if (BitConverter.IsLittleEndian)
                                            Array.Reverse(valueBytes);

                                        value.Add(BitConverter.ToInt32(valueBytes, 0));
                                        offset += 4;
                                    }
                                }
                                else if ((RpmTagType)type == RpmTagType.INT64)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        byte[] valueBytes = new byte[8];
                                        Array.Copy(buff, offset, valueBytes, 0, 8);

                                        if (BitConverter.IsLittleEndian)
                                            Array.Reverse(valueBytes);

                                        value.Add(BitConverter.ToInt64(valueBytes, 0));
                                        offset += 8;
                                    }
                                }

                                if ((RpmTagType)type == RpmTagType.STRING || (RpmTagType)type == RpmTagType.STRING_ARRAY || (RpmTagType)type == RpmTagType.I18NSTRING)
                                {
                                    uint length = 0;

                                    for (int i = 0; i < count; i++)
                                    {
                                        for (uint j = offset; j < buff.Length; j++)
                                        {
                                            if (buff[j] == '\0')
                                            {
                                                length = j - offset;
                                                break;
                                            }
                                        }

                                        byte[] valueBytes = new byte[length];
                                        Array.Copy(buff, offset, valueBytes, 0, length);
                                        value.Add(ASCIIEncoding.ASCII.GetString(valueBytes));

                                        offset += length + 1;
                                    }
                                }
                                else if ((RpmTagType)type == RpmTagType.BIN)
                                {
                                    byte[] valueBytes = new byte[count];
                                    Array.Copy(buff, offset, valueBytes, 0, count);
                                    value.Add(valueBytes);
                                }

                                rpmTag.Add(tagName, value);
                            }
                        }
                        else
                        {
                            throw new Exception(string.Format("Unable to find RPM header in {0}", fileName));
                        }
                    }

                    List<object> packageList = new List<object>();
                    packageList.Add(fileStream.Position);
                    rpmTag.Add("PACKAGE_OFFSET", packageList);
                }

                if (rpmTag.ContainsKey("EPOCH"))
                    rpmTag.Add("SERIAL", rpmTag["EPOCH"]);

                if (rpmTag.ContainsKey("LICENSE"))
                    rpmTag.Add("COPYRIGHT", rpmTag["LICENSE"]);

                if (rpmTag.ContainsKey("PROVIDENAME"))
                    rpmTag.Add("PROVIDES", rpmTag["PROVIDENAME"]);

                if (rpmTag.ContainsKey("OBSOLETENAME"))
                    rpmTag.Add("OBSOLETES", rpmTag["OBSOLETENAME"]);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }

            return rpmTag;
        }
    }
}
